﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.

//
// Revision history:
//
// BD - January 2013 - Created this file.
// ER - July 2013 - Small tweaks.
//

using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

#if NETSTANDARD2_0
using Nuqleon.Reflection.Emit;
#endif

namespace System.Linq.CompilerServices
{
    /// <summary>
    /// Runtime compiler to generate types at runtime.
    /// </summary>
    public class RuntimeCompiler
    {
        #region Fields

        /// <summary>
        /// Cached default constructor for <see cref="CompilerGeneratedAttribute"/>.
        /// </summary>
        private static readonly ConstructorInfo s_compilerGeneratedAttributeCtor = typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes);

        /// <summary>
        /// Singleton instance of an empty byte array.
        /// </summary>
        private static readonly byte[] s_emptyBytes = Array.Empty<byte>();

        /// <summary>
        /// Lazily instantiated module builder.
        /// </summary>
        private readonly Lazy<ModuleBuilder> _moduleBuilder;

        /// <summary>
        /// Number of anonymous types generated by this compiler instance. Used to generate numbered anonymous type names.
        /// </summary>
        private int _anonymousTypeCount;

        /// <summary>
        /// Number of closure types generated by this compiler instance. Used to generate numbered closure type names.
        /// </summary>
        private int _closureTypeCount;

        /// <summary>
        /// Number of record types generated by this compiler instance. Used to generate numbered record type names.
        /// </summary>
        private int _recordTypeCount;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new runtime compiler instance, causing a unique code generation module to be generated.
        /// </summary>
        public RuntimeCompiler() => _moduleBuilder = new Lazy<ModuleBuilder>(GetGeneratedTypesModule);

        #endregion

        #region Methods

        #region Static

        /// <summary>
        /// Creates an anonymous type with the specified properties, with C# language semantics (see remarks).
        /// </summary>
        /// <param name="properties">Sequence with names and types of properties.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// An anonymous type with C# language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// All of the properties on the resulting type are get-only, making instances of the anonymous type immutable.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </remarks>
        public static Type CreateAnonymousType(IEnumerable<KeyValuePair<string, Type>> properties)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewAnonymousTypeBuilder();
            compiler.DefineAnonymousType(builder, props);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates an anonymous type with the specified properties, with Visual Basic language semantics (see remarks).
        /// </summary>
        /// <param name="properties">Sequence with names, types, and custom attributes of properties.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// An anonymous type with C# language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// All of the properties on the resulting type are get-only, making instances of the anonymous type immutable.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </remarks>
        public static Type CreateAnonymousType(IEnumerable<StructuralFieldDeclaration> properties)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewAnonymousTypeBuilder();
            compiler.DefineAnonymousType(builder, props, keys: null);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates an anonymous type with the specified properties, with Visual Basic language semantics (see remarks).
        /// </summary>
        /// <param name="properties">Sequence with names and types of properties.</param>
        /// <param name="keys">Subset of the properties that are used as keys.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// An anonymous type with Visual language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Only the properties specified as keys are immutable; other properties have setters and can be assigned to.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics for the properties specified as keys, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </remarks>
        public static Type CreateAnonymousType(IEnumerable<KeyValuePair<string, Type>> properties, params string[] keys)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewAnonymousTypeBuilder();
            compiler.DefineAnonymousType(builder, props, keys);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates an anonymous type with the specified properties, with Visual Basic language semantics (see remarks).
        /// </summary>
        /// <param name="properties">Sequence with names, types, and custom attributes of properties.</param>
        /// <param name="keys">Subset of the properties that are used as keys.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// An anonymous type with Visual language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Only the properties specified as keys are immutable; other properties have setters and can be assigned to.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics for the properties specified as keys, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </remarks>
        public static Type CreateAnonymousType(IEnumerable<StructuralFieldDeclaration> properties, params string[] keys)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewAnonymousTypeBuilder();
            compiler.DefineAnonymousType(builder, props, keys);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates a closure type with the specified fields.
        /// </summary>
        /// <param name="fields">Fields to declare on the resulting type.</param>
        /// <returns>Closure type with the specified public assignable fields.</returns>
        public static Type CreateClosureType(IEnumerable<KeyValuePair<string, Type>> fields)
        {
            if (fields == null)
                throw new ArgumentNullException(nameof(fields));

            var flds = fields.AsArray();
            CheckAccess(flds);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewClosureTypeBuilder();
            compiler.DefineClosureType(builder, flds);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates a record type with the specified properties.
        /// </summary>
        /// <param name="properties">Properties to declare on the resulting type.</param>
        /// <param name="valueEquality">Indicates whether to use value equality or reference equality.</param>
        /// <returns>Record type with the specified public assignable properties.</returns>
        public static Type CreateRecordType(IEnumerable<KeyValuePair<string, Type>> properties, bool valueEquality)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewRecordTypeBuilder();
            compiler.DefineRecordType(builder, props, valueEquality);
            return builder.CreateType();
        }

        /// <summary>
        /// Creates a record type with the specified properties.
        /// </summary>
        /// <param name="properties">Properties to declare on the resulting type.</param>
        /// <param name="valueEquality">Indicates whether to use value equality or reference equality.</param>
        /// <returns>Record type with the specified public assignable properties.</returns>
        public static Type CreateRecordType(IEnumerable<StructuralFieldDeclaration> properties, bool valueEquality)
        {
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var compiler = new RuntimeCompiler();
            var builder = compiler.GetNewRecordTypeBuilder();
            compiler.DefineRecordType(builder, props, valueEquality);
            return builder.CreateType();
        }

        #endregion

        #region Instance

#pragma warning disable IDE0079 // Remove unnecessary suppression (only on .NET 5.0)
#pragma warning disable CA1822 // Mark static (API compat)

        /// <summary>
        /// Gets a new type builder for an anonymous type.
        /// </summary>
        /// <returns>Type builder instance used to define an anonymous type.</returns>
        /// <remarks>A call to CreateType is required by the caller to perform the final construction step of the type, after using DefineAnonymousType.</remarks>
        public TypeBuilder GetNewAnonymousTypeBuilder()
        {
            var builder = _moduleBuilder.Value.DefineType(Constants.CS_ANONYMOUS_PREFIX + Interlocked.Increment(ref _anonymousTypeCount), TypeAttributes.Class | TypeAttributes.Public);
            builder.SetCustomAttribute(s_compilerGeneratedAttributeCtor, s_emptyBytes);
            return builder;
        }

        /// <summary>
        /// Defines an anonymous type with the specified properties, with C# language semantics (see remarks).
        /// </summary>
        /// <param name="anonymousTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Sequence with names and types of properties.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// <para>
        /// A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.
        /// </para>
        /// <para>
        /// An anonymous type with C# language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// All of the properties on the resulting type are get-only, making instances of the anonymous type immutable.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        public void DefineAnonymousType(TypeBuilder anonymousTypeBuilder, IEnumerable<KeyValuePair<string, Type>> properties)
        {
            if (anonymousTypeBuilder == null)
                throw new ArgumentNullException(nameof(anonymousTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var propertiesWithAttributes = props.Select(kv => new StructuralFieldDeclaration(kv.Key, kv.Value));

            DefineAnonymousTypeImpl(anonymousTypeBuilder, propertiesWithAttributes, keys: null);
        }

        /// <summary>
        /// Defines an anonymous type with the specified properties, with C# language semantics (see remarks).
        /// </summary>
        /// <param name="anonymousTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Sequence with names, types, and custom attribute builders of properties.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// <para>
        /// A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.
        /// </para>
        /// <para>
        /// An anonymous type with C# language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// All of the properties on the resulting type are get-only, making instances of the anonymous type immutable.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        public void DefineAnonymousType(TypeBuilder anonymousTypeBuilder, IEnumerable<StructuralFieldDeclaration> properties)
        {
            if (anonymousTypeBuilder == null)
                throw new ArgumentNullException(nameof(anonymousTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            DefineAnonymousTypeImpl(anonymousTypeBuilder, props, keys: null);
        }

        /// <summary>
        /// Defines an anonymous type with the specified properties, with Visual Basic language semantics (see remarks).
        /// </summary>
        /// <param name="anonymousTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Sequence with names and types of properties.</param>
        /// <param name="keys">Subset of the properties that are used as keys.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// <para>
        /// A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.
        /// </para>
        /// <para>
        /// An anonymous type with Visual Basic language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Only the properties specified as keys are immutable; other properties have setters and can be assigned to.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics for the properties specified as keys, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        public void DefineAnonymousType(TypeBuilder anonymousTypeBuilder, IEnumerable<KeyValuePair<string, Type>> properties, params string[] keys)
        {
            if (anonymousTypeBuilder == null)
                throw new ArgumentNullException(nameof(anonymousTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            var propertiesWithAttributes = props.Select(p => new StructuralFieldDeclaration(p.Key, p.Value));

            DefineAnonymousTypeImpl(anonymousTypeBuilder, propertiesWithAttributes, keys);
        }

        /// <summary>
        /// Defines an anonymous type with the specified properties, with Visual Basic language semantics (see remarks).
        /// </summary>
        /// <param name="anonymousTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Sequence with names, types, and custom attribute builders of properties.</param>
        /// <param name="keys">Subset of the properties that are used as keys.</param>
        /// <returns>Anonymous type with the specified properties.</returns>
        /// <remarks>
        /// <para>
        /// A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.
        /// </para>
        /// <para>
        /// An anonymous type with Visual Basic language semantics has the following properties:
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// Properties are declared in the specified order, which gets reflected in the constructor parameter order and the ToString output.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Only the properties specified as keys are immutable; other properties have setters and can be assigned to.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Equals and GetHashCode methods are generated with property-by-property equality semantics for the properties specified as keys, using the default equality comparer for each property's type.
        /// </description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        public void DefineAnonymousType(TypeBuilder anonymousTypeBuilder, IEnumerable<StructuralFieldDeclaration> properties, params string[] keys)
        {
            if (anonymousTypeBuilder == null)
                throw new ArgumentNullException(nameof(anonymousTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            DefineAnonymousTypeImpl(anonymousTypeBuilder, props, keys);
        }

        private static void DefineAnonymousTypeImpl(TypeBuilder anonymousTypeBuilder, IEnumerable<StructuralFieldDeclaration> properties, string[] keys)
        {
            var propertyDeclarations = default(List<PropertyDeclaration>);

            if (keys != null)
            {
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection - review
                var propertyNames = new HashSet<string>(properties.Select(p => p.Name));
                if (!keys.All(propertyNames.Contains))
                    throw new ArgumentException("One or more specified keys do not appear in the properties list.", nameof(keys));

                propertyDeclarations = properties.Select(p =>
#pragma warning restore CA1851 // Possible multiple enumerations of 'IEnumerable' collection
                {
                    var isKey = keys.Contains(p.Name);
                    return new PropertyDeclaration { Name = p.Name, Type = p.PropertyType, CustomAttributes = p.CustomAttributes, IsKey = isKey, CanRead = true, CanWrite = !isKey };
                }).ToList();
            }
            else
            {
                propertyDeclarations = properties.Select(p => new PropertyDeclaration { Name = p.Name, Type = p.PropertyType, CustomAttributes = p.CustomAttributes, IsKey = true, CanRead = true, CanWrite = false }).ToList();
            }

            new AnonymousTypeGenerator(anonymousTypeBuilder, propertyDeclarations).Build();
        }

        /// <summary>
        /// Gets a new type builder for a closure type.
        /// </summary>
        /// <returns>Type builder instance used to define a closure type.</returns>
        /// <remarks>A call to CreateType is required by the caller to perform the final construction step of the type, after using <see cref="DefineClosureType"/>.</remarks>
        public TypeBuilder GetNewClosureTypeBuilder()
        {
            var builder = _moduleBuilder.Value.DefineType(Constants.CS_CLOSURE_PREFIX + Interlocked.Increment(ref _closureTypeCount), TypeAttributes.Class | TypeAttributes.Public);
            builder.SetCustomAttribute(s_compilerGeneratedAttributeCtor, s_emptyBytes);
            return builder;
        }

        /// <summary>
        /// Creates a closure type with the specified fields.
        /// </summary>
        /// <param name="closureTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="fields">Fields to declare on the resulting type.</param>
        /// <returns>Closure type with the specified public assignable fields.</returns>
        /// <remarks>A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.</remarks>
        public void DefineClosureType(TypeBuilder closureTypeBuilder, IEnumerable<KeyValuePair<string, Type>> fields)
        {
            if (closureTypeBuilder == null)
                throw new ArgumentNullException(nameof(closureTypeBuilder));
            if (fields == null)
                throw new ArgumentNullException(nameof(fields));

            var flds = fields.AsArray();
            CheckAccess(flds);

            foreach (var field in flds)
                closureTypeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
        }

        /// <summary>
        /// Gets a new type builder for a record type.
        /// </summary>
        /// <returns>Type builder instance used to define a record type.</returns>
        /// <remarks>A call to CreateType is required by the caller to perform the final construction step of the type, after using DefineRecordType.</remarks>
        public TypeBuilder GetNewRecordTypeBuilder()
        {
            var builder = _moduleBuilder.Value.DefineType(Constants.RECORD_PREFIX + Interlocked.Increment(ref _recordTypeCount), TypeAttributes.Class | TypeAttributes.Public);
            builder.SetCustomAttribute(s_compilerGeneratedAttributeCtor, s_emptyBytes);
            return builder;
        }

        /// <summary>
        /// Creates a record type with the specified fields.
        /// </summary>
        /// <param name="recordTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Fields to declare on the resulting type.</param>
        /// <param name="valueEquality">Indicates whether to use value equality or reference equality.</param>
        /// <returns>record type with the specified public assignable fields.</returns>
        /// <remarks>A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.</remarks>
        public void DefineRecordType(TypeBuilder recordTypeBuilder, IEnumerable<KeyValuePair<string, Type>> properties, bool valueEquality)
        {
            if (recordTypeBuilder == null)
                throw new ArgumentNullException(nameof(recordTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            new RecordTypeGenerator(recordTypeBuilder, props.Select(p => new PropertyDeclaration { Name = p.Key, Type = p.Value, CustomAttributes = Array.Empty<CustomAttributeDeclaration>(), CanRead = true, CanWrite = true, IsKey = valueEquality }), valueEquality).Build();
        }

        /// <summary>
        /// Creates a record type with the specified fields.
        /// </summary>
        /// <param name="recordTypeBuilder">Type builder to define the anonymous type on.</param>
        /// <param name="properties">Fields to declare on the resulting type.</param>
        /// <param name="valueEquality">Indicates whether to use value equality or reference equality.</param>
        /// <returns>record type with the specified public assignable fields.</returns>
        /// <remarks>A call to CreateType on the TypeBuilder is required by the caller to perform the final construction step of the type. This manual step allows the TypeBuilder to be used for further customization or to declare recursive types.</remarks>
        public void DefineRecordType(TypeBuilder recordTypeBuilder, IEnumerable<StructuralFieldDeclaration> properties, bool valueEquality)
        {
            if (recordTypeBuilder == null)
                throw new ArgumentNullException(nameof(recordTypeBuilder));
            if (properties == null)
                throw new ArgumentNullException(nameof(properties));

            var props = properties.AsArray();
            CheckAccess(props);

            new RecordTypeGenerator(recordTypeBuilder, props.Select(p => new PropertyDeclaration { Name = p.Name, Type = p.PropertyType, CustomAttributes = p.CustomAttributes, CanRead = true, CanWrite = true, IsKey = valueEquality }), valueEquality).Build();
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Gets a new module builder for generated types. Each invocation results in the create of a new dynamic assembly,
        /// which is collectible if the underlying runtime supports it (i.e. on .NET 4.0 and beyond).
        /// </summary>
        /// <returns>Unique module builder for generated types.</returns>
        private static ModuleBuilder GetGeneratedTypesModule()
        {
            var asmn = new AssemblyName("__GeneratedTypes_" + Guid.NewGuid());

#if NETSTANDARD || NET6_0
            var asmb = AssemblyBuilder.DefineDynamicAssembly(
#else
            var asmb = AppDomain.CurrentDomain.DefineDynamicAssembly(
#endif
                asmn,
                AssemblyBuilderAccess.RunAndCollect
            );

            return asmb.DefineDynamicModule("__Module");
        }

        private static void CheckAccess(KeyValuePair<string, Type>[] properties)
        {
            foreach (var property in properties)
            {
                if (!property.Value.IsVisible)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Property '{0}' is of type '{1}' which is not visible outside the defining assembly, and cannot be used for runtime type creation purposes.", property.Key, property.Value));
                }
            }
        }

        private static void CheckAccess(StructuralFieldDeclaration[] properties)
        {
            foreach (var property in properties)
            {
                if (!property.PropertyType.IsVisible)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Property '{0}' is of type '{1}' which is not visible outside the defining assembly, and cannot be used for runtime type creation purposes.", property.Name, property.PropertyType));
                }

                if (property.CustomAttributes != null)
                {
                    foreach (var attribute in property.CustomAttributes)
                    {
                        if (!attribute.CustomAttributeType.IsVisible)
                        {
                            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Property '{0}' uses attribute of type '{1}' which is not visible outside the defining assembly, and cannot be used for runtime type creation purposes.", property.Name, attribute.CustomAttributeType));
                        }
                    }
                }
            }
        }

#pragma warning restore CA1822
#pragma warning restore IDE0079

        #endregion

        #endregion

        #region Helper types

        /// <summary>
        /// Property declaration on a structural type.
        /// </summary>
        private struct PropertyDeclaration
        {
            /// <summary>
            /// Name of the property.
            /// </summary>
            public string Name;

            /// <summary>
            /// Type of the property.
            /// </summary>
            public Type Type;

            /// <summary>
            /// Set of custom attribute builders.
            /// </summary>
            public IEnumerable<CustomAttributeDeclaration> CustomAttributes;

            /// <summary>
            /// Indicates whether the property is readable.
            /// </summary>
            public bool CanRead;

            /// <summary>
            /// Indicates whether the property is writeable.
            /// </summary>
            public bool CanWrite;

            /// <summary>
            /// Indicates whether the property participates in value equality.
            /// </summary>
            public bool IsKey;
        }

        /// <summary>
        /// Helper type to create structural types.
        /// </summary>
        private abstract class StructuralTypeGenerator
        {
            #region Fields

            /// <summary>
            /// Cached default constructor for <see cref="StringBuilder"/>.
            /// </summary>
            private static readonly ConstructorInfo s_stringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes);

            /// <summary>
            /// Cached method info for <see cref="StringBuilder.Append(string)"/>.
            /// </summary>
            private static readonly MethodInfo s_appendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new[] { typeof(string) });

            /// <summary>
            /// Cached method info for <see cref="StringBuilder.Append(object)"/>.
            /// </summary>
            private static readonly MethodInfo s_appendObject = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), new[] { typeof(object) });

            /// <summary>
            /// Cached method info for <see cref="object.ToString()"/>.
            /// </summary>
            private static readonly MethodInfo s_toString = typeof(object).GetMethod(nameof(object.ToString));

            /// <summary>
            /// Type builder to emit members on.
            /// </summary>
            protected readonly TypeBuilder _builder;

            /// <summary>
            /// Sequence of properties, in the requested order of declaration.
            /// </summary>
            protected readonly IEnumerable<PropertyDeclaration> _properties;

            /// <summary>
            /// Mapping of property names to their backing fields.
            /// Contents are initialized by EmitFieldsAndProperties.
            /// </summary>
            protected readonly Dictionary<string, FieldBuilder> _fields;

            #endregion

            #region Constructors

            /// <summary>
            /// Creates a new structural type generator on the specified builder and with the specified properties.
            /// </summary>
            /// <param name="builder">Build to emit the type definition on.</param>
            /// <param name="properties">Properties to declare on the type.</param>
            public StructuralTypeGenerator(TypeBuilder builder, IEnumerable<PropertyDeclaration> properties)
            {
                _builder = builder;
                _properties = properties;
                _fields = new Dictionary<string, FieldBuilder>();
            }

            #endregion

            #region Methods

            /// <summary>
            /// Builds the anonymous type on the TypeBuilder instance.
            /// This doesn't invoke the final CreateType method call.
            /// </summary>
            public void Build()
            {
                EmitFieldsAndProperties();

                //
                // The following should occur *after* the creation of the field declarations.
                //
                EmitConstructors();
                EmitEqualsAndGetHashCode();
                EmitToString();
            }

            //
            // Implementation note:
            //
            //   We can't use the LambdaExpression::Compile(MethodBuilder) method here, because we
            //   need to emit instance methods with accessors to fields. Any attempt to use LINQ
            //   expression trees here would result in parameterized delegate invocations to such
            //   generated methods, which doesn't meet the performance design point here.
            //
            //   Inside the methods below, annotations are made to keep track of the stack depth
            //   of the CLR evaluation stack. Those annotations are prefixes with a method name
            //   in between brackets (e.g. [EQ] for Equals), followed by the current stack depth
            //   *after* running the corresponding line of code. When updating this code, please
            //   keep those annotations current as debugging aids and to check invariants about
            //   the evaluation stack balancing.
            //

            /// <summary>
            /// Emits fields and properties on the TypeBuilder. As a side-effect, the _fields dictionary gets populated.
            /// </summary>
            protected virtual void EmitFieldsAndProperties()
            {
                foreach (var property in _properties)
                {
                    var name = property.Name;
                    var type = property.Type;

                    var field = _builder.DefineField("_" + name, type, !property.CanWrite ? FieldAttributes.Private | FieldAttributes.InitOnly : FieldAttributes.Private);
                    _fields[name] = field;

                    var prop = _builder.DefineProperty(name, PropertyAttributes.None, type, Type.EmptyTypes);

                    foreach (var attribute in property.CustomAttributes)
                    {
                        var customAttributeBuilder = new CustomAttributeBuilder(attribute.Constructor, attribute.Arguments.ToArray());
                        prop.SetCustomAttribute(customAttributeBuilder);
                    }

                    if (property.CanRead)
                    {
                        var propGet = _builder.DefineMethod("get_" + name, MethodAttributes.Public, type, Type.EmptyTypes);

                        var propGetILGen = propGet.GetILGenerator();  // [G] 0
                        propGetILGen.Emit(OpCodes.Ldarg_0);           // [G] 1
                        propGetILGen.Emit(OpCodes.Ldfld, field);      // [G] 1
                        propGetILGen.Emit(OpCodes.Ret);               // [G] 0<-1 (type)
                        prop.SetGetMethod(propGet);
                    }

                    if (property.CanWrite)
                    {
                        var propSet = _builder.DefineMethod("set_" + name, MethodAttributes.Public, typeof(void), new[] { type });

                        var propSetILGen = propSet.GetILGenerator();  // [S] 0
                        propSetILGen.Emit(OpCodes.Ldarg_0);           // [S] 1
                        propSetILGen.Emit(OpCodes.Ldarg_1);           // [S] 2
                        propSetILGen.Emit(OpCodes.Stfld, field);      // [S] 0
                        propSetILGen.Emit(OpCodes.Ret);               // [S] 0<-0 (void)
                        prop.SetSetMethod(propSet);
                    }
                }
            }

            /// <summary>
            /// Emits the constructors on the TypeBuilder.
            /// </summary>
            protected abstract void EmitConstructors();

            /// <summary>
            /// Emits Equals and GetHashCode method overrides on the TypeBuilder.
            /// </summary>
            protected virtual void EmitEqualsAndGetHashCode()
            {
                var keys = _properties.Where(p => p.IsKey).ToList();

                var equals = _builder.DefineMethod(nameof(Equals), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.HasThis, typeof(bool), new[] { typeof(object) });

                var equalsILGen = equals.GetILGenerator();                             // [EQ] 0
                var equalsRightLocal = equalsILGen.DeclareLocal(_builder);

                equalsILGen.Emit(OpCodes.Ldarg_1);                                     // [EQ] 1
                equalsILGen.Emit(OpCodes.Isinst, _builder);                            // [EQ] 1
                equalsILGen.Emit(OpCodes.Stloc, equalsRightLocal);                     // [EQ] 0
                equalsILGen.Emit(OpCodes.Ldloc, equalsRightLocal);                     // [EQ] 1

                var getHashCode = _builder.DefineMethod(nameof(GetHashCode), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.HasThis, typeof(int), Type.EmptyTypes);

                var getHashCodeILGen = getHashCode.GetILGenerator();                   // [HC] 0
                var hashCodeLocal = getHashCodeILGen.DeclareLocal(typeof(int));

                const uint prime = 0xa5555529; // See CompilationPass.cpp in C# compiler codebase.
                var initialHash = 0;
                foreach (var property in keys)
                {
                    var name = property.Name;

#if NET6_0 || NETSTANDARD2_1
                    var nameHash = name.GetHashCode(StringComparison.Ordinal);
#else
                    var nameHash = name.GetHashCode();
#endif

                    initialHash = (int)((prime * initialHash) + nameHash);
                }

                getHashCodeILGen.Emit(OpCodes.Ldc_I4, initialHash);                    // [HC] 1
                getHashCodeILGen.Emit(OpCodes.Stloc, hashCodeLocal);                   // [HC] 0

                if (keys.Count == 0)
                {                                                                      // [EQ] == 1
                    equalsILGen.Emit(OpCodes.Ldnull);                                  // [EQ] 1
                    equalsILGen.Emit(OpCodes.Ceq);                                     // [EQ] 1
                    equalsILGen.Emit(OpCodes.Ldc_I4_0);                                // [EQ] 2
                    equalsILGen.Emit(OpCodes.Ceq);                                     // [EQ] 1
                }
                else
                {                                                                      // [EQ] == 1 && [HC] == 0
                    var fls = equalsILGen.DefineLabel();
                    equalsILGen.Emit(OpCodes.Brfalse, fls);                            // [EQ] 0

                    var i = 0;
                    foreach (var property in keys)
                    {                                                                  // [EQ] == 0 && [HC] == 0
                        var name = property.Name;
                        var type = property.Type;

#if NETSTANDARD2_0
                        type = type.Unwrap();
#endif

                        var field = _fields[name];

                        var compOpen = typeof(EqualityComparer<>);
                        var compClosed = compOpen.MakeGenericType(type);

                        //
                        // When using TypeBuilder-based properties, we need to use different code emission
                        // patterns. This case occurs when declaring recursive anonymous types.
                        //
                        var comparerGetDefault = default(MethodInfo);
                        var comparerEquals = default(MethodInfo);
                        var comparerGetHashCode = default(MethodInfo);
                        if (type.GetType().Namespace == "System.Reflection.Emit")
                        {
                            var compParam = compOpen.GetGenericArguments()[0];
                            comparerGetDefault = TypeBuilder.GetMethod(compClosed, compOpen.GetProperty(nameof(EqualityComparer<object>.Default)).GetGetMethod());
                            comparerEquals = TypeBuilder.GetMethod(compClosed, compOpen.GetMethod(nameof(Equals), new[] { compParam, compParam }));
                            comparerGetHashCode = TypeBuilder.GetMethod(compClosed, compOpen.GetMethod(nameof(GetHashCode), new[] { compParam }));
                        }
                        else
                        {
                            comparerGetDefault = compClosed.GetProperty(nameof(EqualityComparer<object>.Default)).GetGetMethod();
                            comparerEquals = compClosed.GetMethod(nameof(Equals), new[] { type, type });
                            comparerGetHashCode = compClosed.GetMethod(nameof(GetHashCode), new[] { type });
                        }

                        equalsILGen.Emit(OpCodes.Call, comparerGetDefault);            // [EQ] 1     { comparer }
                        equalsILGen.Emit(OpCodes.Ldarg_0);                             // [EQ] 2     { comparer, anon }
                        equalsILGen.Emit(OpCodes.Ldfld, field);                        // [EQ] 2     { comparer, type }
                        equalsILGen.Emit(OpCodes.Ldloc, equalsRightLocal);             // [EQ] 3     { comparer, type, anon }
                        equalsILGen.Emit(OpCodes.Ldfld, field);                        // [EQ] 3     { comparer, type, type }
                        equalsILGen.Emit(OpCodes.Callvirt, comparerEquals);            // [EQ] 1     { bool }

                        if (i != keys.Count - 1)
                            equalsILGen.Emit(OpCodes.Brfalse, fls);                    // [EQ] 0     { }

                        getHashCodeILGen.Emit(OpCodes.Ldc_I4, prime);                  // [HC] 1     { int }
                        getHashCodeILGen.Emit(OpCodes.Ldloc, hashCodeLocal);           // [HC] 2     { int, int }
                        getHashCodeILGen.Emit(OpCodes.Mul);                            // [HC] 1     { int }
                        getHashCodeILGen.Emit(OpCodes.Call, comparerGetDefault);       // [HC] 2     { int, comparer }
                        getHashCodeILGen.Emit(OpCodes.Ldarg_0);                        // [HC] 3     { int, comparer, anon }
                        getHashCodeILGen.Emit(OpCodes.Ldfld, field);                   // [HC] 3     { int, comparer, type }
                        getHashCodeILGen.Emit(OpCodes.Callvirt, comparerGetHashCode);  // [HC] 2     { int, int }
                        getHashCodeILGen.Emit(OpCodes.Add);                            // [HC] 1     { int }
                        getHashCodeILGen.Emit(OpCodes.Stloc, hashCodeLocal);           // [HC] 0     { }

                        i++;
                    }

                    equalsILGen.Emit(OpCodes.Ret);                                     // [EQ] 0<-1 (bool)

                    equalsILGen.MarkLabel(fls);                                        // [EQ] 0
                    equalsILGen.Emit(OpCodes.Ldc_I4_0);                                // [EQ] 1
                }

                equalsILGen.Emit(OpCodes.Ret);                                         // [EQ] 0<-1 (bool)

                getHashCodeILGen.Emit(OpCodes.Ldloc, hashCodeLocal);                   // [HC] 1
                getHashCodeILGen.Emit(OpCodes.Ret);                                    // [HC] 0<-1 (int)
            }

            /// <summary>
            /// Emits the ToString method override on the TypeBuilder.
            /// </summary>
            protected virtual void EmitToString()
            {
                var toString = _builder.DefineMethod(nameof(ToString), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.HasThis, typeof(string), Type.EmptyTypes);

                var toStringILGen = toString.GetILGenerator();                                // [SB] 0

                if (!_properties.Any())
                {
                    toStringILGen.Emit(OpCodes.Ldstr, "{ }");                                 // [SB] 1
                    toStringILGen.Emit(OpCodes.Ret);                                          // [SB] 0<-1 (string)
                }
                else
                {
                    toStringILGen.Emit(OpCodes.Newobj, s_stringBuilderCtor);                  // [SB] 1     { StringBuilder }

                    var toStringSeparator = "{ ";

                    foreach (var property in _properties)
                    {                                                                         // [SB] == 1
                        var name = property.Name;
                        var type = property.Type;
                        var field = _fields[name];

                        toStringILGen.Emit(OpCodes.Ldstr, toStringSeparator + name + " = ");  // [SB] 2     { StringBuilder, string }
                        toStringILGen.Emit(OpCodes.Callvirt, s_appendString);                 // [SB] 1     { StringBuilder }

                        toStringILGen.Emit(OpCodes.Ldarg_0);                                  // [SB] 2     { StringBuilder, anon }
                        toStringILGen.Emit(OpCodes.Ldfld, field);                             // [SB] 2     { StringBuilder, type }
                        if (type.IsValueType)
                            toStringILGen.Emit(OpCodes.Box, type);                            // [SB] 2     { StringBuilder, object }
                        toStringILGen.Emit(OpCodes.Callvirt, s_appendObject);                 // [SB] 1     { StringBuilder }

                        toStringSeparator = ", ";
                    }

                    toStringILGen.Emit(OpCodes.Ldstr, " }");                                  // [SB] 2     { StringBuilder, string }
                    toStringILGen.Emit(OpCodes.Callvirt, s_appendString);                     // [SB] 1     { StringBuilder }

                    toStringILGen.Emit(OpCodes.Callvirt, s_toString);                         // [SB] 1     { string }
                    toStringILGen.Emit(OpCodes.Ret);                                          // [SB] 0<-1 (string)
                }
            }

            #endregion
        }

        /// <summary>
        /// Helper type to create anonymous types.
        /// </summary>
        private sealed class AnonymousTypeGenerator : StructuralTypeGenerator
        {
            /// <summary>
            /// Creates a new anonymous type generator on the specified builder and with the specified properties.
            /// </summary>
            /// <param name="builder">Build to emit the type definition on.</param>
            /// <param name="properties">Properties to declare on the type.</param>
            public AnonymousTypeGenerator(TypeBuilder builder, IEnumerable<PropertyDeclaration> properties)
                : base(builder, properties)
            {
            }

            #region Methods

            /// <summary>
            /// Emits the single constructor on the TypeBuilder.
            /// </summary>
            protected override void EmitConstructors()
            {
                var ctorArgs = _properties.Select(property => property.Type).ToArray();
                var cb = _builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, ctorArgs);

                var ctorILGen = cb.GetILGenerator();       // [C] 0

                int i = 0;
                foreach (var property in _properties)
                {
                    var name = property.Name;
                    var field = _fields[name];

                    ctorILGen.Emit(OpCodes.Ldarg_0);       // [C] 1
                    ctorILGen.Emit(OpCodes.Ldarg, i + 1);  // [C] 2
                    ctorILGen.Emit(OpCodes.Stfld, field);  // [C] 0
                    cb.DefineParameter(i + 1, ParameterAttributes.None, name);

                    i++;
                }

                ctorILGen.Emit(OpCodes.Ret);               // [C] 0<-0 (void)
            }

            #endregion
        }

        /// <summary>
        /// Helper type to create record types.
        /// </summary>
        private sealed class RecordTypeGenerator : StructuralTypeGenerator
        {
            /// <summary>
            /// Cached default constructor for <see cref="object"/>.
            /// </summary>
            private static readonly ConstructorInfo s_objCtor = typeof(object).GetConstructor(Type.EmptyTypes);

            /// <summary>
            /// Indicates whether to emit a record type with value equality semantics.
            /// </summary>
            private readonly bool _valueEquality;

            /// <summary>
            /// Creates a new record type generator on the specified builder and with the specified properties.
            /// </summary>
            /// <param name="builder">Build to emit the type definition on.</param>
            /// <param name="properties">Properties to declare on the type.</param>
            /// <param name="valueEquality">Indicates whether to emit a record type with value equality semantics.</param>
            public RecordTypeGenerator(TypeBuilder builder, IEnumerable<PropertyDeclaration> properties, bool valueEquality)
                : base(builder, properties)
            {
                _valueEquality = valueEquality;
            }

            /// <summary>
            /// Emits the single constructor on the TypeBuilder.
            /// </summary>
            protected override void EmitConstructors()
            {
                var cb = _builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, Type.EmptyTypes);

                var ctorILGen = cb.GetILGenerator();       // [C] 0

                ctorILGen.Emit(OpCodes.Ldarg_0);           // [C] 1
                ctorILGen.Emit(OpCodes.Call, s_objCtor);   // [C] 0
                ctorILGen.Emit(OpCodes.Ret);               // [C] 0<-0 (void)
            }

            /// <summary>
            /// Emits Equals and GetHashCode method overrides on the TypeBuilder if the record type is built with value equality semantics.
            /// </summary>
            protected override void EmitEqualsAndGetHashCode()
            {
                if (_valueEquality)
                {
                    base.EmitEqualsAndGetHashCode();
                }
            }
        }

        #endregion
    }
}
